/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package org.scid.android.gamelogic;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

import android.util.Log;

/**
 * Stores the state of a chess position.
 * All required state is stored, except for all previous positions
 * since the last capture or pawn move. That state is only needed
 * for three-fold repetition draw detection, and is better stored
 * in a separate hash table.
 * @author petero
 */
public class Position {
	private int[] squares;

	public boolean whiteMove;

	/** Bit definitions for the castleMask bit mask. */
    public static final int A1_CASTLE = 0; /** White long castle. */
    public static final int H1_CASTLE = 1; /** White short castle. */
    public static final int A8_CASTLE = 2; /** Black long castle. */
    public static final int H8_CASTLE = 3; /** Black short castle. */

	private int castleMask;

	private int epSquare;

	/** Number of half-moves since last 50-move reset. */
	public int halfMoveClock;

	/** Game move number, starting from 1. */
	public int fullMoveCounter;

	private long hashKey; // Cached Zobrist hash key
	private int wKingSq, bKingSq; // Cached king positions

	/** Game half move number, starting from 1. */
	public int halfMoveCounter;

	/** Initialize board to empty position. */
	public Position() {
		squares = new int[64];
		for (int i = 0; i < 64; i++)
			squares[i] = Piece.EMPTY;
		whiteMove = true;
		castleMask = 0;
		epSquare = -1;
		halfMoveClock = 0;
		halfMoveCounter = 1;
		fullMoveCounter = 1;
		hashKey = computeZobristHash();
		wKingSq = bKingSq = -1;
	}

	public Position(Position other) {
		squares = new int[64];
		System.arraycopy(other.squares, 0, squares, 0, 64);
		whiteMove = other.whiteMove;
		castleMask = other.castleMask;
		epSquare = other.epSquare;
		halfMoveClock = other.halfMoveClock;
		fullMoveCounter = other.fullMoveCounter;
		halfMoveCounter = other.halfMoveCounter;
		hashKey = other.hashKey;
		wKingSq = other.wKingSq;
		bKingSq = other.bKingSq;
	}

	@Override
	public boolean equals(Object o) {
		if ((o == null) || (o.getClass() != this.getClass()))
			return false;
		Position other = (Position) o;
		if (!drawRuleEquals(other))
			return false;
		if (halfMoveClock != other.halfMoveClock)
			return false;
		if (fullMoveCounter != other.fullMoveCounter)
			return false;
		if (halfMoveCounter != other.halfMoveCounter)
			return false;
		if (hashKey != other.hashKey)
			return false;
		return true;
	}

	@Override
	public int hashCode() {
		return (int) hashKey;
	}

	/**
     * Return Zobrish hash value for the current position.
     * Everything except the move counters are included in the hash value.
	 */
	public final long zobristHash() {
		return hashKey;
	}

	/**
     * Decide if two positions are equal in the sense of the draw by repetition rule.
	 * @return True if positions are equal, false otherwise.
	 */
	final public boolean drawRuleEquals(Position other) {
		for (int i = 0; i < 64; i++) {
			if (squares[i] != other.squares[i])
				return false;
		}
		if (whiteMove != other.whiteMove)
			return false;
		if (castleMask != other.castleMask)
			return false;
		if (epSquare != other.epSquare)
			return false;
		return true;
	}

	public final void setWhiteMove(boolean whiteMove) {
		if (whiteMove != this.whiteMove) {
			hashKey ^= whiteHashKey;
			this.whiteMove = whiteMove;
		}
	}
	/** Return index in squares[] vector corresponding to (x,y). */
	public final static int getSquare(int x, int y) {
		return y * 8 + x;
	}
	/** Return x position (file) corresponding to a square. */
	public final static int getX(int square) {
		return square & 7;
	}
	/** Return y position (rank) corresponding to a square. */
	public final static int getY(int square) {
		return square >> 3;
	}
	/** Return true if (x,y) is a dark square. */
	public final static boolean darkSquare(int x, int y) {
		return (x & 1) == (y & 1);
	}

	/** Return piece occuping a square. */
	public final int getPiece(int square) {
		if (square < 64) {
			return squares[square];
		} else {
			Log.e("SCID", "square was:" + square);
			return Piece.EMPTY;
		}
	}
	/** Set a square to a piece value. */
	public final void setPiece(int square, int piece) {
		// Update hash key
		int oldPiece = squares[square];
		hashKey ^= psHashKeys[oldPiece][square];
		hashKey ^= psHashKeys[piece][square];

		// Update board
		squares[square] = piece;

		// Update king position
		if (piece == Piece.WKING) {
			wKingSq = square;
		} else if (piece == Piece.BKING) {
			bKingSq = square;
		}
	}

	/** Return true if white long castling right has not been lost. */
	public final boolean a1Castle() {
		return (castleMask & (1 << A1_CASTLE)) != 0;
	}
	/** Return true if white short castling right has not been lost. */
	public final boolean h1Castle() {
		return (castleMask & (1 << H1_CASTLE)) != 0;
	}
	/** Return true if black long castling right has not been lost. */
	public final boolean a8Castle() {
		return (castleMask & (1 << A8_CASTLE)) != 0;
	}
	/** Return true if black short castling right has not been lost. */
	public final boolean h8Castle() {
		return (castleMask & (1 << H8_CASTLE)) != 0;
	}
	/** Bitmask describing castling rights. */
	public final int getCastleMask() {
		return castleMask;
	}
	public final void setCastleMask(int castleMask) {
		hashKey ^= castleHashKeys[this.castleMask];
		hashKey ^= castleHashKeys[castleMask];
		this.castleMask = castleMask;
	}

	/** En passant square, or -1 if no ep possible. */
	public final int getEpSquare() {
		return epSquare;
	}
	public final void setEpSquare(int epSquare) {
		if (this.epSquare != epSquare) {
            hashKey ^= epHashKeys[(this.epSquare >= 0) ? getX(this.epSquare) + 1 : 0];
			hashKey ^= epHashKeys[(epSquare >= 0) ? getX(epSquare) + 1 : 0];
			this.epSquare = epSquare;
		}
	}


	public final int getKingSq(boolean whiteMove) {
		return whiteMove ? wKingSq : bKingSq;
	}

	/**
	 * Count number of pieces of a certain type.
	 */
	public final int nPieces(int pType) {
		int ret = 0;
		for (int sq = 0; sq < 64; sq++) {
			if (squares[sq] == pType)
				ret++;
		}
		return ret;
	}

	/** Apply a move to the current position. */
	public final void makeMove(Move move, UndoInfo ui) {
		ui.capturedPiece = squares[move.to];
		ui.castleMask = castleMask;
		ui.epSquare = epSquare;
		ui.halfMoveClock = halfMoveClock;
		boolean wtm = whiteMove;

		int p = squares[move.from];
		int capP = squares[move.to];

		boolean nullMove = (move.from == 0) && (move.to == 0);

        if (nullMove || (capP != Piece.EMPTY) || (p == (wtm ? Piece.WPAWN : Piece.BPAWN))) {
			halfMoveClock = 0;
		} else {
			halfMoveClock++;
		}
		if (!wtm) {
			fullMoveCounter++;
		}
		halfMoveCounter++;

		// Handle castling
		int king = wtm ? Piece.WKING : Piece.BKING;
		int k0 = move.from;
		if (p == king) {
			if (move.to == k0 + 2) { // O-O
				setPiece(k0 + 1, squares[k0 + 3]);
				setPiece(k0 + 3, Piece.EMPTY);
			} else if (move.to == k0 - 2) { // O-O-O
				setPiece(k0 - 1, squares[k0 - 4]);
				setPiece(k0 - 4, Piece.EMPTY);
			}
			if (wtm) {
				setCastleMask(castleMask & ~(1 << Position.A1_CASTLE));
				setCastleMask(castleMask & ~(1 << Position.H1_CASTLE));
			} else {
				setCastleMask(castleMask & ~(1 << Position.A8_CASTLE));
				setCastleMask(castleMask & ~(1 << Position.H8_CASTLE));
			}
		}
		if (!nullMove) {
			int rook = wtm ? Piece.WROOK : Piece.BROOK;
			if (p == rook) {
				removeCastleRights(move.from);
			}
			int oRook = wtm ? Piece.BROOK : Piece.WROOK;
			if (capP == oRook) {
				removeCastleRights(move.to);
			}
		}

		// Handle en passant and epSquare
		int prevEpSquare = epSquare;
		setEpSquare(-1);
		if (p == Piece.WPAWN) {
			if (move.to - move.from == 2 * 8) {
				int x = Position.getX(move.to);
                if (    ((x > 0) && (squares[move.to - 1] == Piece.BPAWN)) ||
                        ((x < 7) && (squares[move.to + 1] == Piece.BPAWN))) {
					setEpSquare(move.from + 8);
				}
			} else if (move.to == prevEpSquare) {
				setPiece(move.to - 8, Piece.EMPTY);
			}
		} else if (p == Piece.BPAWN) {
			if (move.to - move.from == -2 * 8) {
				int x = Position.getX(move.to);
                if (    ((x > 0) && (squares[move.to - 1] == Piece.WPAWN)) ||
                        ((x < 7) && (squares[move.to + 1] == Piece.WPAWN))) {
					setEpSquare(move.from - 8);
				}
			} else if (move.to == prevEpSquare) {
				setPiece(move.to + 8, Piece.EMPTY);
			}
		}

		// Perform move
		setPiece(move.from, Piece.EMPTY);
		// Handle promotion
		if (move.promoteTo != Piece.EMPTY) {
			setPiece(move.to, move.promoteTo);
		} else {
			setPiece(move.to, p);
		}
		setWhiteMove(!wtm);
	}

	public final void unMakeMove(Move move, UndoInfo ui) {
		setWhiteMove(!whiteMove);
		int p = squares[move.to];
		setPiece(move.from, p);
		setPiece(move.to, ui.capturedPiece);
		setCastleMask(ui.castleMask);
		setEpSquare(ui.epSquare);
		halfMoveClock = ui.halfMoveClock;
		boolean wtm = whiteMove;
		if (move.promoteTo != Piece.EMPTY) {
			p = wtm ? Piece.WPAWN : Piece.BPAWN;
			setPiece(move.from, p);
		}
		if (!wtm) {
			fullMoveCounter--;
		}
		halfMoveCounter--;

		// Handle castling
		int king = wtm ? Piece.WKING : Piece.BKING;
		int k0 = move.from;
		if (p == king) {
			if (move.to == k0 + 2) { // O-O
				setPiece(k0 + 3, squares[k0 + 1]);
				setPiece(k0 + 1, Piece.EMPTY);
			} else if (move.to == k0 - 2) { // O-O-O
				setPiece(k0 - 4, squares[k0 - 1]);
				setPiece(k0 - 1, Piece.EMPTY);
			}
		}

		// Handle en passant
		if (move.to == epSquare) {
			if (p == Piece.WPAWN) {
				setPiece(move.to - 8, Piece.BPAWN);
			} else if (p == Piece.BPAWN) {
				setPiece(move.to + 8, Piece.WPAWN);
			}
		}
	}

	private final void removeCastleRights(int square) {
		if (square == Position.getSquare(0, 0)) {
			setCastleMask(castleMask & ~(1 << Position.A1_CASTLE));
		} else if (square == Position.getSquare(7, 0)) {
			setCastleMask(castleMask & ~(1 << Position.H1_CASTLE));
		} else if (square == Position.getSquare(0, 7)) {
			setCastleMask(castleMask & ~(1 << Position.A8_CASTLE));
		} else if (square == Position.getSquare(7, 7)) {
			setCastleMask(castleMask & ~(1 << Position.H8_CASTLE));
		}
	}

	/* ------------- Hashing code ------------------ */

	private static long[][] psHashKeys; // [piece][square]
	private static long whiteHashKey;
	private static long[] castleHashKeys; // [castleMask]
	private static long[] epHashKeys; // [epFile + 1] (epFile==-1 for no ep)

	static {
		psHashKeys = new long[Piece.nPieceTypes][64];
		castleHashKeys = new long[16];
		epHashKeys = new long[9];
		int rndNo = 0;
		for (int p = 0; p < Piece.nPieceTypes; p++) {
			for (int sq = 0; sq < 64; sq++) {
				psHashKeys[p][sq] = getRandomHashVal(rndNo++);
			}
		}
		whiteHashKey = getRandomHashVal(rndNo++);
		for (int cm = 0; cm < castleHashKeys.length; cm++)
			castleHashKeys[cm] = getRandomHashVal(rndNo++);
		for (int f = 0; f < epHashKeys.length; f++)
			epHashKeys[f] = getRandomHashVal(rndNo++);
	}

	/**
     * Compute the Zobrist hash value non-incrementally. Only useful for test programs.
	 */
	final long computeZobristHash() {
		long hash = 0;
		for (int sq = 0; sq < 64; sq++) {
			int p = squares[sq];
			hash ^= psHashKeys[p][sq];
		}
		if (whiteMove)
			hash ^= whiteHashKey;
		hash ^= castleHashKeys[castleMask];
		hash ^= epHashKeys[(epSquare >= 0) ? getX(epSquare) + 1 : 0];
		return hash;
	}

	private final static long getRandomHashVal(int rndNo) {
		try {
			MessageDigest md = MessageDigest.getInstance("SHA-1");
			byte[] input = new byte[4];
			for (int i = 0; i < 4; i++)
				input[i] = (byte) ((rndNo >> (i * 8)) & 0xff);
			byte[] digest = md.digest(input);
			long ret = 0;
			for (int i = 0; i < 8; i++) {
				ret ^= ((long) digest[i]) << (i * 8);
			}
			return ret;
		} catch (NoSuchAlgorithmException ex) {
			throw new UnsupportedOperationException("SHA-1 not available");
		}
	}

	/** Useful for debugging. */
	public final String toString() {
		return TextIO.asciiBoard(this);
	}
}
